package helper.scxml.scxml2

import helper.base.A2LHM
import helper.base.LHMHelper.A2LHMExpand.deepClone
import helper.base.LHMHelper.A2LHMExpand.toStr
import helper.base.LHMHelper.A3LHM
import helper.base.StringHelper.removeEmptyLine
import helper.base.debug.DebugHelper.DebuggerList
import helper.base.debug.DebugHelper.DebuggerList.Companion.makeDebuggerList
import helper.base.debug.DebugHelper.LevelDebugger.Companion.debugger_0
import helper.base.debug.DebugHelper.LevelDebugger.Companion.debugger_1
import helper.base.debug.DebugHelper.LevelDebugger.Companion.getLevelDebugger
import helper.base.math.DoubleMatrixHelper.DoubleMatrix
import helper.base.string.ToLStringHelper.toL1String
import helper.base.string.ToStrHelper.IToStr
import helper.base.string.ToStrHelper.TabStringUnit.Companion.makeTabStringUnit
import helper.base.string.ToStrHelper.toSBStr
import helper.scxml.ScxmlVarHelper.ClockConstraint
import helper.scxml.scxml2.EnvHelper.RunResult.Companion.makeRunResult
import helper.scxml.scxml2.Expand.DataExpand.exprToInt
import helper.scxml.scxml2.Expand.DataExpand.setExprAddIncrement
import helper.scxml.scxml2.Expand.DataExpand.setExprAddOne
import helper.scxml.scxml2.Expand.SCXMLExecutorExpand.isInState
import helper.scxml.scxml2.Expand.ToStr.toStr
import helper.scxml.scxml2.MathHelper.DSSUnit.Companion.getDSSUnit
import helper.scxml.scxml2.MathHelper.LHMToExpand.toDoubleArrayList
import helper.scxml.scxml2.MathHelper.LocationEventVListLHM
import helper.scxml.scxml2.MathHelper.LocationEventVListLHM.Companion.makeLocationEventVListLHM
import helper.scxml.scxml2.StrategyTripleHelper.IRenEventSelector
import helper.scxml.scxml2.StrategyTripleHelper.StateRenEventSelector
import helper.scxml.scxml2.StrategyTripleHelper.StrategyTuple
import helper.scxml.scxml2.StrategyTripleHelper.Type2StrategyTuple
import org.apache.commons.scxml2.SCXMLExecutor
import org.apache.commons.scxml2.model.Data

object EnvHelper {
    abstract class T1BaseEnv(
        envStateConstraintLHM: A2LHM<String, ClockConstraint>,
        envEventLHM: A3LHM<String, String, Double>,
        getIRenEventSelectorFun: (SCXMLTuple) -> IRenEventSelector,
    ) : StrategyTuple(
        envStateConstraintLHM,
        envEventLHM,
        getIRenEventSelectorFun,
    ) {
        abstract val scxmlTuple: SCXMLTuple

        val executor: SCXMLExecutor
            get() = scxmlTuple.executor

        val dataSCXML: DataSCXML
            get() = scxmlTuple.dataSCXML

        val dataGlobalTime: Data
            get() {
                return dataSCXML.getData(Res.globalTimeId)!!
            }

        val dataGlobalTimeInt: Int
            get() {
                return dataGlobalTime.exprToInt()
            }

        val ifOnRenState: Boolean
            get() {
                return scxmlTuple.renStateList.contains(scxmlTuple.activeStatesString)
            }

        val statusString: String
            get() {
                return scxmlTuple.getStatusString()
            }

        fun getRenEvent(
            stateId: String,
        ): String? {
            return getRenEvent(scxmlTuple, stateId)
        }

        fun reset() {
            scxmlTuple.reset()
        }

        fun toStr(tabNum: Int = 0): String {
            val tabNum1 = tabNum + 1
            val tabNum2 = tabNum + 2
            val tabNumStr = "\t".repeat(tabNum)
            val tabNumStr1 = "\t".repeat(tabNum1)
            val sb = StringBuilder()
            sb.append("${tabNumStr}env:\n")
            sb.append(this.scxmlTuple.toStr(tabNum1))
            sb.append("${tabNumStr1}envStateConstraintLHM:\n")
            sb.append(envStateConstraintLHM.toStr(tabNum2))
            sb.append("${tabNumStr1}envEventLHM:\n")
            sb.append(envEventLHM.toStr(tabNum2))
            sb.append("${tabNumStr1}getIRenEventSelectorFun:")
            val iRenEventSelector = getIRenEventSelectorFun(scxmlTuple)
            if (iRenEventSelector is StateRenEventSelector) {
                sb.append("isStateRenEventSelector:\n")
                sb.append(iRenEventSelector.renEventLHM.toStr(tabNum2))
            } else {
                sb.append("\n")
            }
            return sb.toString()
        }

        fun isInFinalState(): Boolean {
            scxmlTuple.finalStateList.map {
                if (executor.isInState(it)) return true
            }
            return false
        }

        open val ifMachineTimeMax: Boolean
            get() {
                return false
            }

        open val ifDone: Boolean
            get() {
                if (isInFinalState()) return true
                if (ifMachineTimeMax) return true
                return false
            }
    }

    data class StateEventDataUnit(
        val location: String,
        val action: String,
        val data: A2LHM<String, String> = A2LHM(),
    ) : IToStr {
        companion object {
            fun makeStateEventDataUnit(
                state: String,
                event: String,
                data: A2LHM<String, String> = A2LHM(),
            ): StateEventDataUnit {
                return StateEventDataUnit(state, event, data)
            }
        }

        override fun toStr(tabNum: Int): String {
            val sb = StringBuilder()
            val tsu = makeTabStringUnit(tabNum)
            val temp = "(${location},${action},${data})"
            sb.append("${tsu.tabNumStr0}${temp}\n")
            return sb.toString()
        }

        // LocationActionClockUnit的深拷贝方法
        fun deepClone() = makeStateEventDataUnit(location, action, data.deepClone())

        fun toL1String(): String {
            val sb = StringBuilder()
            sb.append("LocationActionClockUnit(")
            sb.append("location=$location,")
            sb.append("action=$action,")
            sb.append("data(size=${data.size})=[")
            data.forEach { (t, u) ->
                sb.append("${t}=${u},")
            }
            sb.append("]")
            sb.append(")")
            return sb.toString()
        }
    }

    data class RunResult(
        val us: ArrayList<StateEventDataUnit> = ArrayList(),
        val endStateList: ArrayList<String> = ArrayList(),
        val endData: A2LHM<String, String> = A2LHM(),
    ) : IToStr {
        companion object {
            var counter: Int = 0
            val debugger = getLevelDebugger()

            fun makeRunResult(
                us: ArrayList<StateEventDataUnit> = ArrayList(),
                endStateList: ArrayList<String> = ArrayList(),
                endData: A2LHM<String, String> = A2LHM(),
            ): RunResult {
                return RunResult(
                    us, endStateList, endData
                )
            }
        }

        init {
            counter++
        }

        object Expand {
            fun ArrayList<RunResult>.toFormattedString(maxPrintNum: Int = this.size): String {
                val sb = StringBuilder()
                sb.append("ArrayList<RunResult>(size=${this.size})=[\n")
                this.withIndex().forEach { (i, it) ->
                    if (i >= maxPrintNum) return@forEach
                    sb.append("${it.toL2String().prependIndent("\t")}\n")
                }
                if (maxPrintNum < this.size) {
                    sb.append("\t...\n")
                }
                sb.append("]\n")
                return sb.toString()
            }
        }

        fun updateWhenRunEnd(scxmlTuple: SCXMLTuple) {
            scxmlTuple.activeStates.map {
                it.id
            }.map {
                this.endStateList.add(it)
            }
            scxmlTuple.toData(this.endData)
        }

        fun toL2String(): String {
            val sb = StringBuilder()
            sb.append("RunResult(\n")

            sb.append("\tus(size=${us.size})=[\n")
            us.forEach {
                sb.append("\t\t${it.toL1String()}\n")
            }
            sb.append("\t]\n")

            sb.append("\tendStateList=${endStateList.toL1String()}\n")
            sb.append("\tendData=${endData.toL1String()}\n")
            sb.append(")")
            return sb.toString()
        }

        override fun toStr(tabNum: Int): String {
            val sb = StringBuilder()
            val tsu = makeTabStringUnit(tabNum)
            sb.append("${tsu.tabNumStr0}RunResult(\n")
            us.toSBStr(sb, tsu, "us", 1)
            endStateList.toSBStr(sb, tsu, "endStateList", 1)
            endData.toSBStr(sb, tsu, "endData", 1)
            sb.append("${tsu.tabNumStr0})\n")
            return sb.toString().removeEmptyLine()
        }

        // RunResult的深拷贝方法
        fun deepClone(): RunResult {
            val clonedUs = ArrayList<StateEventDataUnit>()
            for (unit in us) {
                clonedUs.add(unit.deepClone())
            }

            val clonedEndStateList = ArrayList<String>(endStateList)

            return makeRunResult(
                clonedUs,
                clonedEndStateList,
                endData.deepClone(),
            )
        }
    }

    object Expand {
        fun List<RunResult>.toLocationEventVListLHM(
            keys: List<String>,
            doDoubleMatrixFun: DoubleMatrix.() -> DoubleMatrix = {
                this
            },
        ): LocationEventVListLHM {
            val lhm = makeLocationEventVListLHM()
            this.map {
                it.us.map {
                    lhm.addIfAbsent(
                        it.location,
                        it.action,
                        getDSSUnit(doDoubleMatrixFun = doDoubleMatrixFun),
                    )
                    lhm[it.location]!![it.action]!!.dss.add(it.data.toDoubleArrayList(keys))
                }
            }
            return lhm
        }
    }

    //把策略传入环境，这样做是不对的
    abstract class T3BaseEnv {
        abstract val strategyTuple: Type2StrategyTuple

        fun getEnvEvent(stateId: String): String? {
            return strategyTuple.getEnvEvent(scxmlTuple, stateId)
        }

        fun getRenEvent(stateId: String): String? {
            return strategyTuple.getRenEvent(scxmlTuple, stateId)
        }

        abstract val scxmlTuple: SCXMLTuple

        val executor: SCXMLExecutor
            get() = scxmlTuple.executor

        val dataSCXML: DataSCXML
            get() = scxmlTuple.dataSCXML

        val dataGlobalTime: Data
            get() {
                return dataSCXML.getData(Res.globalTimeId)!!
            }

        val dataGlobalTimeInt: Int
            get() {
                return dataGlobalTime.exprToInt()
            }

        val ifOnRenState: Boolean
            get() {
                return scxmlTuple.renStateList.contains(scxmlTuple.activeStatesString)
            }

        val statusString: String
            get() {
                return scxmlTuple.getStatusString()
            }

        fun reset() {
            scxmlTuple.reset()
        }

        fun toStr(tabNum: Int = 0): String {
            val tabNum1 = tabNum + 1
            val tabNum2 = tabNum + 2
            val tabNumStr = "\t".repeat(tabNum)
            val tabNumStr1 = "\t".repeat(tabNum1)
            val sb = StringBuilder()
            sb.append("${tabNumStr}env:\n")
            sb.append(this.scxmlTuple.toStr(tabNum1))
            sb.append("${tabNumStr1}getIEnvEventSelectorFun\n")
            sb.append("${tabNumStr1}getIRenEventSelectorFun\n")
            return sb.toString()
        }

        fun isInFinalState(): Boolean {
            scxmlTuple.finalStateList.map {
                if (executor.isInState(it)) return true
            }
            return false
        }

        open val ifMachineTimeMax: Boolean
            get() {
                return false
            }

        open val ifDone: Boolean
            get() {
                if (isInFinalState()) return true
                if (ifMachineTimeMax) return true
                return false
            }

        fun getEvent(): String? {
            scxmlTuple.activeStates.map {
                it.id
            }.map { stateId ->
                if (isInFinalState()) return@map
                getEnvEvent(stateId)?.let { event ->
                    return event
                }
                getRenEvent(stateId)?.let { event ->
                    return event
                }
            }
            return null
        }

        //如果有event就fire出去
        fun strategyFireEvent(
            countClockValueFun: (SCXMLTuple, String) -> Unit = { _, _ -> },
            debuggerList: DebuggerList = makeDebuggerList(debugger_0),
        ): String? {
            debuggerList.startPln(
                "getEvent",
                debugger_0,
            )
            val event: String? = getEvent()
            debuggerList.endPln(
                debugger_0,
            )
            if (event != null) {
                //fire出去了
                scxmlTuple.fireEvent(
                    event = event,
                    countClockValueFun = countClockValueFun,
                    debuggerList = debuggerList,
                )
                if (!ifOnRenState) {
                    strategyFireEvent(
                        countClockValueFun = countClockValueFun,
                        debuggerList = debuggerList,
                    )
                }
            }
            return event
        }

        fun step(
            countClockValueFun: (SCXMLTuple, String) -> Unit = { _, _ -> },
            debuggerList: DebuggerList = makeDebuggerList(debugger_0),
        ) {
            if (ifDone) return

            debuggerList.startPln(
                "strategyFireEvent",
                debugger_0,
            )

            val event: String? = strategyFireEvent(
                countClockValueFun = countClockValueFun,
                debuggerList = debuggerList,
            )
            debuggerList.endPln(
                debugger_0,
            )
            if (event != null) {
                return
            }
            debuggerList.pln(
                "addTime",
                debugger_0,
            )
            scxmlTuple.activeStateIds.map { stateId ->
                scxmlTuple.stateDataIncrementLHM[stateId]?.map { (dataKey, increment) ->
                    dataSCXML.getData(dataKey)?.setExprAddIncrement(increment.toInt())
                }
            }
            dataGlobalTime.setExprAddOne()
            if (ifMachineTimeMax) {
                return
            }
        }

        fun stepToNeedRen(
            countClockValueFun: (SCXMLTuple, String) -> Unit = { _, _ -> },
            debuggerList: DebuggerList = makeDebuggerList(debugger_0),
        ) {
            if (ifDone) return
            debuggerList.startPln(
                "stepToNeedRen",
                debugger_0,
            )
            var hasStepOne = false
            while (true) {
                if (ifDone) break
                if (hasStepOne) {
                    if (ifOnRenState) {
                        debuggerList.pln(
                            "ifOnRenState=$ifOnRenState",
                            debugger_0,
                        )
                        break
                    }
                }
                debuggerList.startPln(
                    "step",
                    debugger_0,
                )
                step(
                    countClockValueFun = countClockValueFun,
                    debuggerList = debuggerList,
                )
                debuggerList.endPln(
                    debugger_0,
                )
                if (!hasStepOne) {
                    hasStepOne = true
                }
            }
            debuggerList.endPln(
                debugger_0,
            )
        }

        fun taskRun(
            runResult: RunResult = makeRunResult(),
            countClockValueFun: (SCXMLTuple, String) -> Unit = { _, _ -> },
            debuggerList: DebuggerList = makeDebuggerList(debugger_0),
        ): RunResult {
            fun debugPlnStatus() {
                debuggerList.pln(
                    statusString,
                    debugger_0,
                    debugger_1,
                )
            }

            reset()
            executor.go()

            debugPlnStatus()
            while (true) {
                if (ifDone) break
                stepToNeedRen(
                    countClockValueFun = countClockValueFun,
                    debuggerList = debuggerList,
                )
            }
            debugPlnStatus()
            runResult.updateWhenRunEnd(scxmlTuple)
            return runResult
        }

        fun taskRun2(
            runResult: RunResult = makeRunResult(),
            debuggerList: DebuggerList = makeDebuggerList(debugger_0),
        ): RunResult {
            reset()
            return this.taskRun(
                runResult = runResult,
                countClockValueFun = { scxmlTuple, event ->
                    val state = scxmlTuple.activeStatesString
                    if (this.scxmlTuple.renStateList.contains(state)) {
                        runResult.us.add(
                            StateEventDataUnit(
                                state,
                                event,
                                scxmlTuple.toData(),
                            )
                        )
                    }
                },
                debuggerList = debuggerList,
            )
        }
    }
}